﻿using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Communication.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Base;
using UtilZ.Dotnet.Ex.Communication.Net.Tcp;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// WebSocket客户端
    /// </summary>
    public class WebSocketClient : ClientAbs, IDisposable
    {
        private readonly HttpConnection _connection;
        private readonly string _remoteEndPointStr;
        private readonly Encoding _websocketTextEncoding;


        /// <summary>
        /// websocket消息输出事件
        /// </summary>
        public event EventHandler<MessageOutputArgs> MessageOutput = null;

        /// <summary>
        /// websocket连接断开通知事件
        /// </summary>
        public event EventHandler<NetConnectedChangedArgs> ConnectionChanged = null;
        private bool _onRaiseDisconnected = false;
        private void OnRaiseDisconnect()
        {
            try
            {
                if (this._onRaiseDisconnected)
                {
                    return;
                }

                this._onRaiseDisconnected = true;

                var handler = this.ConnectionChanged;
                if (handler != null)
                {
                    handler(this, new NetConnectedChangedArgs(ConnectionStatus.Closed, DisconnectMode.Active, this._connection.LinkInfo.LocalEndPoint, this._connection.LinkInfo.RemoteEndPoint));
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }


        private readonly ThreadEx _receiveDataThread;

        private int _bufferSize = NetHelper.TCP_PACKAGE;
        /// <summary>
        /// 获取或设置接收数据缓存空间大小,默认MTU最大值
        /// </summary>
        public int BufferSize
        {
            get { return _bufferSize; }
            set { _bufferSize = value; }
        }


        /// <summary>
        /// 获取指示当前客户端与服务端口的连接状态
        /// </summary>
        public override bool Connected
        {
            get
            {
                return this._connection.Connected;
            }
        }


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="connection">通信Socket连接</param>
        /// <param name="initPara"></param>
        /// <param name="websocketTextEncoding"></param>
        internal WebSocketClient(HttpConnection connection, NetTcpClientInitPara initPara, Encoding websocketTextEncoding)
            : base(initPara)
        {
            initPara.Unpacker.PackOutput += Unpacker_PackOutput;
            this._connection = connection;
            this._websocketTextEncoding = websocketTextEncoding == null ? Encoding.UTF8 : websocketTextEncoding;
            this._remoteEndPointStr = NetHelper.GetIPPortString(connection.LinkInfo.RemoteEndPoint);
            this._receiveDataThread = new ThreadEx(this.ReceiveDataThreadMethod, $"{nameof(WebSocketClient)}:{this._remoteEndPointStr}接收数据线程");
        }

        private void Unpacker_PackOutput(object sender, DataOutputArgs e)
        {
            try
            {
                if (e == null)
                {
                    this.OnRaiseDisconnect();
                    return;
                }

                var args = (WebSocketDataOutputArgs)e;
                DataSource data = args.Data;
                var linkInfo = (NetLinkInfo)args.LinkInfo;
                switch (args.Opcode)
                {
                    case HttpOpcodes.TEXT_FRAME:
                        string text = this._websocketTextEncoding.GetString(data.Bytes);
                        this.OnRaiseDataOutput(linkInfo, new WebSocketTextMessage(text, this._websocketTextEncoding));
                        break;
                    case HttpOpcodes.BINARY_FRAME:
                        IWebSocketMessage binaryMessage;
                        switch (data.DataType)
                        {
                            case DataSourceType.Bytes:
                                binaryMessage = new WebSocketBinaryMessage(data.Bytes, data.Offset, data.Length);
                                break;
                            case DataSourceType.File:
                                binaryMessage = new WebSocketBinaryMessage(data.FilePath, 0, data.LongLength);
                                break;
                            //#if CORE
                            //                            case BufferDataType.Memory:
                            //                                binaryMessage = new WebSocketBinaryMessage(data.MemoryBuffer.ToArray(), (int)data.Offset, (int)data.Length);
                            //                                break;
                            //#endif
                            default:
                                throw new NotImplementedException(data.DataType.ToString());
                        }
                        this.OnRaiseDataOutput(linkInfo, binaryMessage);
                        break;
                    case HttpOpcodes.CLOSE:
                        this.ProcessClose();
                        break;
                    case HttpOpcodes.PING:
                        //需要回应
                        this.ProcessPing();
                        break;
                    case HttpOpcodes.PONG:
                        //需回通知外部
                        this.ProcessPong();
                        break;
                    default:
                        //预留
                        ZLoger.Warn($"收到预留Opcode{args.Opcode}");
                        break;
                }
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }

        private void OnRaiseDataOutput(NetLinkInfo linkInfo, IWebSocketMessage message)
        {
            this.MessageOutput?.Invoke(this, new MessageOutputArgs(linkInfo, message));
        }

        private void ProcessPong()
        {
            //ZLoger.Trace($"websocket收到来自{this._remoteEndPointStr}的pong消息");
        }

        private void ProcessPing()
        {
            //ZLoger.Trace($"websocket收到来自{this._remoteEndPointStr}的ping消息");
            this.SendMessage(new WebSocketPongMessage());
        }

        private int _closeMessageCount = 0;
        private void ProcessClose()
        {
            if (this._closeMessageCount == 0)
            {
                //如果多次发送关闭消息的话,客户端会报错
                var closeMessage = new WebSocketCloseMessage();
                this.SendMessage(closeMessage);
            }
            this._closeMessageCount++;

            //注：websocket关闭消息客户端会发送两次。第一次为请求，第二次为确认.当收到确认消息时才关闭服务端
            if (this._closeMessageCount >= 2)
            {
                this.OnRaiseDisconnect();
            }
        }


        private void ReceiveDataThreadMethod(ThreadPara threadPara)
        {

            try
            {
                byte[] buffer = new byte[this._bufferSize];

                using (var reader = new BinaryReader(new MemoryStream(buffer)))
                {
                    var unpackerPara = new NetTcpUnpackerPara();
                    unpackerPara.Client = this;
                    unpackerPara.LinkInfo = this._connection.LinkInfo;
                    unpackerPara.Buffer = buffer;
                    unpackerPara.Reader = reader;

                    int revLen;
                    int offset = 0;
                    int preReceiveSize = this._bufferSize < NetHelper.TCP_PACKAGE ? this._bufferSize : NetHelper.TCP_PACKAGE;
                    int totalDataLength = 0;
                    int unpackedDataLength;
                    IUnpacker unpacker = base._initPara.Unpacker;

                    while (true)
                    {
                        try
                        {
                            if (this._disposed || threadPara.Token.IsCancellationRequested)
                            {
                                break;
                            }

                            try
                            {
                                revLen = this._connection.Read(buffer, offset, preReceiveSize);
                            }
                            catch (ObjectDisposedException)
                            {
                                break;
                            }
                            catch (ArgumentOutOfRangeException)
                            {
                                //客户端断开了连接
                                break;
                            }
                            catch (IOException ioex)
                            {
                                ZLoger.Warn(ioex, $"WebSocketClient\"{this._remoteEndPointStr}\"连接断开");
                                break;
                            }
                            catch (SocketException sex)
                            {
                                if (NetHelper.SocketErrorIsDisconnect(sex.SocketErrorCode))
                                {
                                    ZLoger.Info($"WebSocketClient\"{this._remoteEndPointStr}\"连接断开");
                                }
                                else
                                {
                                    ZLoger.Warn(sex, $"{nameof(WebSocketClient)}:{this._remoteEndPointStr}接收数据异常");
                                }
                                break;
                            }
                            catch (Exception exi)
                            {
                                ZLoger.Warn(exi, $"{nameof(WebSocketClient)}:{this._remoteEndPointStr}接收数据异常");
                                break;
                            }

                            if (revLen > 0)
                            {
                                totalDataLength += revLen;
                                unpackerPara.Offset = (int)reader.BaseStream.Position;
                                unpackerPara.DataLength = totalDataLength;
                                unpackedDataLength = unpacker.UnPackge(unpackerPara);
                                totalDataLength -= unpackedDataLength;

                                offset += revLen;
                                if (buffer.Length - offset < preReceiveSize)
                                {
                                    if (totalDataLength > 0)
                                    {
                                        var parseOffset = (int)reader.BaseStream.Position;
                                        Array.Copy(buffer, parseOffset, buffer, 0, totalDataLength);
                                    }

                                    reader.BaseStream.Position = 0L;
                                    offset = totalDataLength;
                                }
                            }
                            else
                            {
                                //if (!client.Connected || !(client.Poll(1, SelectMode.SelectRead) && client.Available == 0))
                                //{
                                //    //对方连接意外断开,接收长度为0却没有报异常
                                //    ZLoger.Warn($"接收数据长度为:{len},_clientDisconnectNotify,client.Poll=true && client.Available == 0");
                                this.OnRaiseDisconnect();
                                break;
                                //    }
                                //    else
                                //    {
                                //        //client.Blocking
                                //        client.Send(_testClientBuffer, 0, 0);
                                //        ZLoger.Warn($"接收数据长度为:{len}_clientDisconnectNotify.client.Poll=false");
                                //    }
                            }
                        }
                        catch (ArgumentOutOfRangeException)
                        {
                            totalDataLength = 0;
                            reader.BaseStream.Position = 0L;
                            offset = 0;
                        }
                        catch (SocketException)
                        {
                            break;
                        }
                        catch (ObjectDisposedException)
                        {
                            break;
                        }
                        catch (ThreadAbortException)
                        {
                            break;
                        }
                        catch (Exception ex)
                        {
                            ZLoger.Error(ex, "接收数据异常");
                        }
                    }
                }
            }
            catch (ThreadAbortException)
            { }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }


        /// <summary>
        /// 开启数据接收线程
        /// </summary>
        public void StartReceive()
        {
            this._receiveDataThread.Start();
        }


        private IWebSocketMessage _noControlMessage = null;
        private readonly object _noControlMessageLock = new object();
        private readonly object _controlMessageLock = new object();

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="message">消息</param>
        public void SendMessage(IWebSocketMessage message)
        {
            if (message == null)
            {
                return;
            }

            try
            {
                var opcode = message.GetOpcode();
                if (HttpOpcodes.IsControlMessage(opcode))
                {
                    lock (this._noControlMessageLock)
                    {
                        if (this._noControlMessage != null)
                        {
                            this._noControlMessage.Pause();
                        }

                        try
                        {
                            message.Send(this._connection.Stream);
                        }
                        finally
                        {
                            if (this._noControlMessage != null)
                            {
                                this._noControlMessage.Resume();
                            }
                        }
                    }
                }
                else
                {
                    //加this._controlMessageLock锁表示非控制消息必须按先后顺序发送,否则数据就错乱了
                    lock (this._controlMessageLock)
                    {
                        lock (this._noControlMessageLock)
                        {
                            this._noControlMessage = message;
                        }

                        try
                        {
                            message.Send(this._connection.Stream);
                        }
                        finally
                        {
                            lock (this._noControlMessageLock)
                            {
                                this._noControlMessage = null;
                            }
                        }
                    }
                }

                message.Dispose();
            }
            catch (IOException)
            {

            }
            catch (SocketException)
            {

            }
            catch (ObjectDisposedException)
            {

            }
        }

        /// <summary>
        /// websocket发送文本数据
        /// </summary>
        /// <param name="text">文本</param>
        public void Send(string text)
        {
            this.SendMessage(new WebSocketTextMessage(text, this._websocketTextEncoding));
        }

        /// <summary>
        /// websocket发送二进制数据
        /// </summary>
        /// <param name="buffer">数据buffer</param>
        /// <param name="offset">在文件中数据起始位置,小于等于0表示从0开始</param>
        /// <param name="length">数据长度,小于等于0表示到末尾</param>
        public void Send(byte[] buffer, int offset = -1, int length = -1)
        {
            this.SendMessage(new WebSocketBinaryMessage(buffer, offset, length));
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="filePath">文件</param>
        /// <param name="offset">在文件中数据起始位置,小于等于0表示从0开始</param>
        /// <param name="length">数据长度,小于等于0表示到末尾</param>
        public void Send(string filePath, int offset = -1, int length = -1)
        {
            this.SendMessage(new WebSocketBinaryMessage(filePath, offset, length));
        }

        /// <summary>
        /// 发送ping消息-目前是只要发送了ping消息,连接就会被浏览器关闭.
        /// 估计控制码什么的没对,暂时没时间研究了,以后再说
        /// </summary>
        public void Ping()
        {
            var pingMessage = new WebSocketPingMessage();
            this.SendMessage(pingMessage);
        }





        /// <summary>
        /// Dispose
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        protected override void Dispose(bool disposing)
        {
            this.ProcessClose();
            this._receiveDataThread.Dispose();
            this._connection.Dispose();
            this.OnRaiseDisconnect();
            this._initPara.Unpacker.Dispose();
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="bufferData">要发送的数据对象</param>
        /// <param name="options">发送选项</param>
        protected override void PrimitiveSend(DataSource bufferData, ISendOptions options)
        {
            if (bufferData == null)
            {
                return;
            }

            if (bufferData.DataType == DataSourceType.Bytes)
            {
                this.Send(bufferData.Bytes, bufferData.Offset, bufferData.Length);
            }
            else
            {
                throw new NotImplementedException($" WebSocket发送数据类型\"{bufferData.DataType.ToString()}\"未实现");
            }
        }
    }
}
